import logging
import os
import sys
import threading
from logging.handlers import RotatingFileHandler
from os.path import exists
from traceback import format_exception
from typing import Dict, Union

from flask import has_app_context, has_request_context, request

from app.common.libs import auth
from app.models.logModel import LogDetailModel, LogModel
from configs.settings import BASE_DIR
from tools import db
from utils.utils import get_conversion_ip


class DatabaseLogHandler(logging.Handler):
    @property
    def in_celery(self):
        if os.environ.get("FLASK_CONTEXT_IN_CELERY", None) == "true":
            return True
        return False

    def get_operator_name(self):
        if self.in_celery:
            return "celery"
        user = auth.current_user()
        return user.username if user else None

    @staticmethod
    def get_request_info() -> Dict:
        if not has_request_context():
            return {"ip": get_conversion_ip("127.0.0.1"), "url_path": "", "method": ""}
        return {
            "ip": get_conversion_ip(),
            "url_path": request.path,
            "method": request.method,
        }

    @staticmethod
    def get_request_headers() -> Union[Dict, None]:
        if not has_request_context():
            return None
        return {key: val for key, val in request.headers.items() if key.istitle()}

    def emit(self, record: logging.LogRecord) -> None:

        # 未进入flask上下文时不进行日志记录
        if not has_app_context():
            return
        # 尝试验证当前session是否可用并提交未完成事务
        else:
            try:
                db.session.commit()
            except Exception as e:
                print("日志记录初始化时数据库连接异常", e)
                db.session.close()

        tb = sys.exc_info()
        source_tb = None
        if all(tb):
            tb_obj = tb[2]
            while tb_obj.tb_next:
                source_tb = tb_obj.tb_next
                tb_obj = source_tb
        try:
            if source_tb:
                level = "ERROR"
                message = ""
                # 异常信息
                traceback = "".join(
                    format_exception(tb[0], tb[1], source_tb, limit=None, chain=True)
                )
                # 文件行
                file_lineno = source_tb.tb_lineno
                # 方法
                func_name = ".".join(
                    [
                        source_tb.tb_frame.f_globals["__name__"],
                        source_tb.tb_frame.f_code.co_name,
                    ]
                )
                thread_id = threading.get_ident()
                pid = os.getpid()
            else:
                level = record.levelname
                message = record.getMessage()
                traceback = None
                func_name = (
                    record.pathname.replace(str(BASE_DIR), "")
                    .lstrip("/")
                    .rstrip("py")
                    .replace("/", ".")
                    + record.funcName
                )
                file_lineno = record.levelno
                thread_id = record.thread
                pid = record.process

            log_info = {
                **self.get_request_info(),
                "pid": pid,
                "thread_id": thread_id,
                "level": level,
                "operator_name": self.get_operator_name(),
                "func_name": func_name,
                "file_lineno": file_lineno,
            }
            log_obj = LogModel(**log_info)
            db.session.add(log_obj)
            db.session.flush()

            log_detail_info = {
                "log_id": log_obj.id,
                "headers": self.get_request_headers(),
                "message": message,
                "traceback": traceback,
            }
            db.session.add(LogDetailModel(**log_detail_info))
            db.session.commit()
        except RuntimeError:  # noqa
            pass
        except Exception as e:  # noqa
            db.session.rollback()
            print(f"日志记录异常: {e}")


class LogConfig:
    def __init__(self, app):
        # Formatter
        formatter = logging.Formatter(
            "%(asctime)s %(levelname)s %(process)d %(thread)d %(pathname)s %(lineno)s %(message)s"
        )

        log_path = app.config["LOG_PATH"]
        if not exists(log_path):
            os.makedirs(log_path)

        if app.config["DEBUG"]:
            # FileHandler Info
            info_log_path = app.config["LOG_PATH_INFO"]
            file_handler_info = RotatingFileHandler(filename=info_log_path)
            file_handler_info.setFormatter(formatter)
            file_handler_info.setLevel(logging.INFO)

            info_filter = InfoFilter()
            file_handler_info.addFilter(info_filter)
            app.logger.addHandler(file_handler_info)

            # FileHandler Error
            error_log_path = app.config["LOG_PATH_ERROR"]
            file_handler_error = RotatingFileHandler(filename=error_log_path)
            file_handler_error.setFormatter(formatter)
            file_handler_error.setLevel(logging.ERROR)
            app.logger.addHandler(file_handler_error)

        # 输出到数据库
        dbh = DatabaseLogHandler()
        dbh.setLevel(logging.WARNING)
        app.logger.addHandler(dbh)


class InfoFilter(logging.Filter):
    def filter(self, record):
        """only use INFO"""
        if logging.INFO <= record.levelno < logging.ERROR:
            return super().filter(record)
        else:
            return 0
